from atb import _atb
from atb._atb import atb_dll
import ctypes


class ATBException(Exception):
    pass


def check_error(status, error=0):
    """ checks a status code and raises an ATBException with the error message
    if the status appears to be an error
    """
    if status == error:
        raise ATBException(atb_dll.TwGetLastError())

def init():
    """ initilizes atb. 
    you must call this once at the start of your application
    """
    check_error( atb_dll.TwInit(_atb.TW_OPENGL, 0) )

def shutdown():
    """ shutsdown atb.
    you should call this once at the end of your application
    """
    check_error( atb_dll.TwTerminate() )

def delete_all():
    """ removes all current bars """
    check_error( atb_dll.TwDeleteAllBars() )

def draw():
    """ draws all bars """
    check_error( atb_dll.TwDraw() )

def resize(width, height):
    """ resizes the area used by atb.
    you should call this everytime the window size changes
    """
    check_error( atb_dll.TwWindowSize(width, height) )

def get_top():
    """ returns the top most bar or None if no bars are visible """
    bar = atb_dll.TwGetTopBar()
    if bar:
        return Bar._all_bars[bar]
    return None

def dict_to_defs(args):
    """ converts a dictionary like {a:'b', 1:2} to the string "a=b 1=2"
    suitable for passing to your_bar.define
    automatic type conversion is done as follows:
        if the value is bool result is simply the name of the string eg
        {'closed':True} -> "closed"
        if the value is a tuple the items are converted to strings and joined
        by spaces, eg {'size':(10, 20)} -> "size='10 20'"
    """
    r = []
    for k, v in args.iteritems():
        if type(v) is bool:
            v = ""
        elif type(v) is tuple:
            v = "='%s'" % " ".join((str(i) for i in v))
        else:
            v = "='%s'" % str(v)
        r.append(k+v)

    return " ".join(r)


class Bar(object):
    """ main atb Bar class """

    # ctypes types mapped to ATB types used to try 
    # and automatically guess the correct type 
    _type_map = {
        ctypes.c_int: _atb.TW_TYPE_INT16,
        ctypes.c_long: _atb.TW_TYPE_INT32,
        ctypes.c_float: _atb.TW_TYPE_FLOAT,      
        ctypes.c_float * 3: _atb.TW_TYPE_COLOR3F,
        ctypes.c_float * 4: _atb.TW_TYPE_COLOR4F,
    }
    _type_map_inv = dict([(v, k) for k, v in _type_map.iteritems()])

    # a dict of all know bars 
    _all_bars = {}

    def __init__(self, title=None, **defs):
        if not title:
            title = "unnamed_%d" % len(Bar._all_bars)
        self._bar = atb_dll.TwNewBar(title)
        if defs:
            def_str = dict_to_defs(defs)
            self.define(title + " " + def_str)
        self._all_bars[self._bar] = self
        self._c_callbacks = []
        self._all_vars = []

    def __del__(self):
        del Bar._all_bars[self._bar]
        check_error(atb_dll.TwDeleteBar(self._bar))

    def __len__(self):
        return len(self._all_vars)

    def getName(self):
        """ returns the name of this bar
        also available using the name property
        """
        r = atb_dll.TwGetBarName(self._bar)
        check_error(r, None)
        return r
    name = property(getName)

    def clear(self):
        """ removes all variables, buttons and seperators """
        check_error(atb_dll.TwRemoveAllVars(self._bar))
        self._all_vars = []

    def remove(self, name):
        """ removes a single variable, button or seperator """
        check_error(atb_dll.TwRemoveVar(self._bar, name))
        self._all_vars.remove(name)

    def bring_to_front(self):
        """ moves this bar to the front of the screen """
        check_error(atb_dll.TwSetTopBar(self._bar))

    def add_var(self, name, value=None, 
                typ=None, 
                read_only=False,
                getter=None, setter=None, data=None, 
                wrap_funcs=True, inv_type=None,
                **defs):
        """ adds a variable to this bar.
        value must be a ctypes instance. 

        typ is either a ctypes type class or a TW_TYPE_* constant. if
        a ctypes class is passed the dict Bar._type_map is used to 
        try and find a suitable TY_TYPE_* constant.
        you do not need to passtyp if value's type is one of the 
        mapped types.

        read_only is a boolean to make the variable read only.

        the getter and setter arguments allow you to use functions to 
        get and set a value. if only a getter is passed the variable
        is read only. the getter and setter functions are wrapped by
        default to make it easier to use getters and setters using the 
        inv_type to automatically convert the type back to a python type. 
        this behaviour can be disabled by passing wrap_funcs = False. 
        data is used for application specific data and will be passed 
        to the both the getter and setter.

        further configuration of the variable can be set by passing 
        more keyword arguments using any of the variables listed at
        http://www.antisphere.com/Wiki/tools:anttweakbar:varparamsyntax
        """
        if typ is None and value is not None:
            typ = self._type_map.get(type(value))
        elif typ:
            typ = self._type_map.get(typ, typ)

        if typ is None:
            raise ValueError("type is None")
        if inv_type is None:
            inv_type = self._type_map_inv.get(typ)
        
        def_str = dict_to_defs(defs)

        if wrap_funcs:
            if getter:
                def wrapped_getter(p, user_data):
                    v = ctypes.cast(p, ctypes.POINTER(inv_type))
                    d = ctypes.cast(user_data, ctypes.py_object)
                    v[0] = getter(d.value)

            if setter:
                def wrapped_setter(p, user_data):
                    v = ctypes.cast(p, ctypes.POINTER(inv_type))
                    d = ctypes.cast(user_data, ctypes.py_object)
                    setter(v[0], d.value)
        else:
            wrapped_getter = getter
            wrapped_setter = setter

        if (getter and read_only) or (getter and not setter):
            c_callback = _atb.GETFUNC(wrapped_getter)
            self._c_callbacks.append(c_callback)
            r = atb_dll.TwAddVarCB(self._bar, name, typ, None, c_callback, 
                                ctypes.py_object(data), def_str)
        elif (getter and setter):
            c_setter = _atb.SETFUNC(wrapped_setter)
            c_getter = _atb.GETFUNC(wrapped_getter)
            self._c_callbacks.extend((c_setter, c_getter))
            r = atb_dll.TwAddVarCB(self._bar, name, typ, c_setter, c_getter, 
                                ctypes.py_object(data), def_str)
        elif read_only:
            r = atb_dll.TwAddVarRO(self._bar, name, typ, ctypes.byref(value), def_str)
        else:
            r = atb_dll.TwAddVarRW(self._bar, name, typ, ctypes.byref(value), def_str)

        check_error(r)

        self._all_vars.append(name)

    def add_button(self, name, callback, data=None, wrap_callback=True, **defs):
        """ adds a button to this bar
        callback can be any python callable that accepts one argument ( 
        the data keyword argument )
        to make callbacks easier the callback function is wrapped, this 
        behaviour can be disabled by passing wrap_callback=False.
        """
        if wrap_callback:
            def wrapped_callback(userdata):
                d = ctypes.cast(userdata, ctypes.py_object)
                callback(d.value)
        else:
            wrapped_callback = callback

        c_callback = _atb.BUTTONFUNC(wrapped_callback)
        self._c_callbacks.append(c_callback)
        def_str = dict_to_defs(defs)
        data_p = ctypes.py_object(data)
        check_error( atb_dll.TwAddButton(self._bar, name, c_callback, data_p, def_str) )
        self._all_vars.append(name)

    def add_separator(self, name, **defs):
        """ adds a seperator to this bar """
        def_str = dict_to_defs(defs)
        check_error( atb_dll.TwAddSeparator(self._bar, name, def_str ) )
        self._all_vars.append(name)

    def define(self, *defs, **kwargs):
        """ sets options either on the bar itself or one of the bar's buttons, 
        seperators, or variables.
        see http://www.antisphere.com/Wiki/tools:anttweakbar:varparamsyntax
        and http://www.antisphere.com/Wiki/tools:anttweakbar:twbarparamsyntax
        """
        for d in defs:
            check_error( atb_dll.TwDefine(d) )
        if kwargs:
            check_error( atb_dll.TwDefine(d) )


class DictBar(Bar, dict):
    """ dict bar is an easy way to create many values """

    _python_ctypes_map = {
        float: ctypes.c_float,
        int: ctypes.c_int,
    }

    def __init__(self, title=None, values={}, **defs):
        Bar.__init__(self, title, **defs)
        dict.__init__(self)

        for key, value in values.iteritems():
            self[key] = value

    def __setitem__(self, item, value):
        if item not in self._all_vars:
            typ = self._python_ctypes_map[type(value)]
            val = typ(value)
            dict.__setitem__(self, item, val)
            self.add_var(item, val, typ=typ)        
        else:
            dict.__getitem__(self, item).value = value

    def __getitem__(self, item):
        return dict.__getitem__(self, item).value
    




